package com.battle.service.interceptor;

import cn.hutool.core.util.ReflectUtil;
import com.battle.pojo.check.SensitiveEntity;
import com.battle.pojo.check.SensitiveField;
import com.battle.service.check.EncryptionUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.*;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Objects;

/**
 * @author anni.zhu
 * @author anni.zhu@king-v.com
 * Created in 2024/11/25 14:03
 */
@Slf4j
@Component
@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),
        @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),
        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
public class MyBatisInterceptor implements Interceptor {

    @Resource
    private EncryptionUtil util;

    /**
     * 拦截方法
     *
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object target = invocation.getTarget();
        //拦截sql结果处理器
        if (target instanceof ResultSetHandler) {
            return resultDecrypt(invocation);
        }
        //拦截sql参数处理器
        if (target instanceof ParameterHandler) {
            return parameterEncrypt(invocation);
        }
        //拦截sql语句处理器
        if (target instanceof StatementHandler) {
            return replaceSql(invocation);
        }
        return invocation.proceed();
    }

    /**
     * 对mybatis映射结果进行字段解密
     *
     * @param invocation
     * @return
     * @throws Throwable
     */
    private Object resultDecrypt(Invocation invocation) throws Throwable {
        //取出查询的结果
        Object resultObject = invocation.proceed();
        if (Objects.isNull(resultObject)) {
            return null;
        }
        //基于selectList
        if (resultObject instanceof ArrayList) {
            ArrayList resultList = (ArrayList) resultObject;
            if (CollectionUtils.isEmpty(resultList)) {
                return resultObject;
            }
            for (Object result : resultList) {
                if (needToDecrypt(result)) {
                    //逐一解密
                    decrypt(result);
                }
            }
            //基于selectOne
        } else {
            if (needToDecrypt(resultObject)) {
                //逐一解密
                decrypt(resultObject);
            }
        }
        return resultObject;
    }

    /**
     * mybatis映射参数进行加密
     *
     * @param invocation 参数
     * @return 结果
     * @throws Throwable 异常
     */
    private Object parameterEncrypt(Invocation invocation) throws Throwable {
        //@Signature 指定了 type= parameterHandler 后，这里的 invocation.getTarget() 便是parameterHandler
        //若指定ResultSetHandler ，这里则能强转为ResultSetHandler
        ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
        // 获取参数对像，即 mapper 中 paramsType 的实例
        Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
        parameterField.setAccessible(true);
        //取出实例
        Object parameterObject = parameterField.get(parameterHandler);

        if(parameterHandler.getParameterObject() instanceof MapperMethod.ParamMap){
            MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap) parameterHandler.getParameterObject();
            if (null == paramMap) {
                return invocation.proceed();
            }
            //判断ParamMap有param1这个key才去获取
            boolean param1 = paramMap.containsKey("param1");
            if (param1) {
                parameterObject = paramMap.get("param1");
            }else{
                return invocation.proceed();
            }
        }
        if (null == parameterObject) {
            return invocation.proceed();
        }
        Class<?> parameterObjectClass = parameterObject.getClass();
        //校验该实例的类是否被@SensitiveEntity所注解
        SensitiveEntity sensitiveEntity = AnnotationUtils.findAnnotation(parameterObjectClass, SensitiveEntity.class);
        //未被@SensitiveEntity所注解 则为null
        if (Objects.isNull(sensitiveEntity)) {
            return invocation.proceed();
        }
        //取出当前当前类所有字段，传入加密方法
        //Field[] declaredFields = parameterObjectClass.getDeclaredFields();
        Field[] allFields = ReflectUtil.getFields(parameterObjectClass);
        encrypt(allFields, parameterObject);
        return invocation.proceed();
    }

    /**
     * 替换mybatis Sql中的加密Key
     *
     * @param invocation 参数
     * @return 结果
     * @throws Throwable 异常
     */
    private Object replaceSql(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        BoundSql boundSql = statementHandler.getBoundSql();
        //获取到原始sql语句
        String sql = boundSql.getSql();
        if (null == sql){
            return invocation.proceed();
        }
        //通过反射修改sql语句
        Field field = boundSql.getClass().getDeclaredField("sql");
        field.setAccessible(true);
        field.set(boundSql, sql);
        return invocation.proceed();
    }


    public <T> T encrypt(Field[] declaredFields, T paramsObject) throws Exception {
        for (Field field : declaredFields) {

            SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class);
            if (Objects.isNull(sensitiveField)) {
                continue;
            }
            field.setAccessible(true);
            Object object = field.get(paramsObject);
            if (object instanceof String) {
                String value = (String) object;
                //如果映射字段值为空则跳过不进行加密
                if (StringUtils.isEmpty(value)) {
                    continue;
                }
                //加密
                field.set(paramsObject, util.encryption(value));
            }
        }
        return paramsObject;

    }

    /**
     * 判断是否包含需要加解密对象
     *
     * @param object 参数
     * @return 结果
     */
    private boolean needToDecrypt(Object object) {
        if(Objects.isNull(object)){
            return false;
        }
        Class<?> objectClass = object.getClass();
        Class<?> parentClass = objectClass.getSuperclass();
        SensitiveEntity sensitiveEntity = AnnotationUtils.findAnnotation(objectClass, SensitiveEntity.class);
        SensitiveEntity parentSensitiveEntity = AnnotationUtils.findAnnotation(parentClass, SensitiveEntity.class);
        return Objects.nonNull(sensitiveEntity) || Objects.nonNull(parentSensitiveEntity);
    }


    public <T> T decrypt(T result) throws Exception {
        //取出resultType的类
        Class<?> resultClass = result.getClass();
        Field[] allFields = ReflectUtil.getFields(resultClass);
        //Field[] declaredFields = resultClass.getDeclaredFields();
        for (Field field : allFields) {
            //取出所有被SensitiveField注解的字段
            SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class);
            if (Objects.isNull(sensitiveField)) {
                continue;
            }
            field.setAccessible(true);
            Object object = field.get(result);
            if (object instanceof String) {
                String value = (String) object;
                //如果映射字段值为空,并且不已==结尾则跳过不进行解密
                if (StringUtils.isEmpty(value) || value==null) {
                    continue;
                }
                //调用解密方法
                String decrpytValue=util.decode(value).toString();
                field.set(result, decrpytValue);
            }
        }
        return result;
    }


    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

}
